perm filename GRAPHS.PAL[HAL,HE]6 blob
sn#167940 filedate 1975-07-09 generic text, type C, neo UTF8
COMMENT ⊗ VALID 00014 PAGES
C REC PAGE DESCRIPTION
C00001 00001
C00002 00002 Data structures, GSINIT
C00006 00003 NXTTIM
C00007 00004 INVLDT
C00008 00005 CHANGE, CHNGER
C00014 00006 ADDCHG
C00016 00007 GETVAL, GETVR0
C00018 00008 EVALND, EVLCLC
C00023 00009 ADDCLC
C00025 00010 MAKEGN, DELGN, DELGN1
C00032 00011 RLLST1, RLLST2
C00034 00012 GET2WD, GET3WD
C00035 00013 W2SPC, W3SPC, MP2WD, MP3WD
C00037 00014 Known Bugs
C00038 ENDMK
C⊗;
; Data structures, GSINIT
.SBTTL Graph routines.
;Graph structure definitions
;RHT 9/74 RF 6/75
COMMENT ⊗
This is the runtime's prime evil,
The murderous graph nodes and interlocks.
⊗
;GRAPH NODES ;Explicitly released, formed from large block store.
II==0
XX NXTGN ;Links all graph nodes. Points to next one.
XX PRVGN ;Previous link in that chain
XX INVMRK ;0 => valid, other => invalid
XX VALIDF ;a count which is incremented at every revaluation
XX GNVAL ;points at the value cell
XX GNDEPS ;list of dependent nodes. (singly linked, absolute
; pointers, explicitly reclaimed)
XX GNCLCS ;list of calculator cells. (see format below)
XX GNCHGS ;list of change cells. (see format below)
GNDSIZ == II/2 ;Length of graph cell (in words)
;CELL LINKS
II==0
XX DATUM ;Information
XX LINKF ;Forward link
XX LINKB ;Backward link, if any. Singly linked chains don't use it.
;CALCULATOR CELL ;Explicitly released, formed from large block store.
II==0
XX NXTCLC ;next calculator cell in chain
XX NEEDED ;list of needed nodes. Each is a cell link whose datum
; is an absolute pointer and which is only forward linked.
XX CLCISB ;Points to interpreter status block to resolve addressing
XX CLCIPC ;the interpeter PC where the calculation starts
CLCCSZ == II/2 ;Size of calculator cell, in words
;CHANGER CELL ;Explicitly released, formed from large block store.
II==0
XX NXTCHG ;next changer cell in chain
XX CHGISB ;Points to interpreter status block to resolve addressing
XX CHGIPC ;the interpeter PC where the calculation starts
CHGCSZ == II/2 ;Size of changer cell, in words
GNODES: .BLKW 1 ;head of chain of graph nodes.
TIME: 0 ;used during evaluation of nodes
GNEVT: .BLKW 1 ;event for interlocking graph references
GSINIT:
;Initialize the graph structure to a null situation;
EVMAK ;Make a new interlock event.
MOV (SP),GNEVT;
EVSIG ;Give it one signal.
CLR GNODES;
CLR TIME;
RTS PC ;Done
; NXTTIM
COMMENT ⊗
JSR PC,NXTTIM
Returns TIME←TIME+1 in R0. If TIME goes negative then set all
positive mark cells to negative, then set time to 1. ⊗
NXTTIM: INC TIME ;TIME←TIME+1
MOV TIME,R0
BGT NXT.RT ;OK?
MOV GNODES,R0 ;
BEQ NXTT.3 ;DID WE HAVE ANY??
NXTT.1: TST INVMRK(R0) ;YES
BLE NXTT.2 ;WAS INVMRK POSITIVE
NEG INVMRK(R0) ;YES, NEGATE IT
NXTT.2: MOV NXTGN(R0),R0 ;GO ON TO NEXT
BNE NXTT.1 ;IF ANY
NXTT.3: INC R0 ;R0←0+1
MOV R0,TIME ;TIME IS 1 AGAIN
NXT.RT: RTS PC
; INVLDT
ROUTINE INVLDT,<INV.ND>
MOV INV.ND(RF),R0
JSR PC,INVLR0
RTS RF
INVLR0: TST INVMRK(R0) ;IS IT DEAD YET?
BNE INVL.R ;ALREADY INVALID??
INVL.1: DEC INVMRK(R0) ;NO, MAKE IT SO
MOV R2,-(SP) ;SAFE REGISTER
MOV GNDEPS(R0),R2 ;DEPENDENTS
BEQ INVL.X ;IF ANY
INVL.2: MOV DATUM(R2),R0 ;GET A DEPENDENT
JSR PC,INVLR0 ;AND INVALIDATE IT
MOV LINKF(R2),R2 ;GO TO NEXT
BNE INVL.2 ;IF ANY
INVL.X: MOV (SP)+,R2 ;GET BACK SCRATCH REGISTER
INVL.R: RTS PC
; CHANGE, CHNGER
COMMENT ⊗ Called by the outside world to put a new value, CHG.VNEW,
in the graph node CHG.ND. Returns with CHG.ND in R0. ⊗
ROUTINE CHANGE,<CHG.ND,CHG.VNEW>
MOV R2,-(SP) ;Save R2
MOV R3,-(SP) ;Save R3
MOV CHG.ND(RF),R0 ;R0 ← the target node.
EVWAIT GNEVT ;Wait until OK to enter critical code.
JSR PC,INVLR0 ;invalidate it for the nonce
MOV CHG.ND(RF),R0 ;R0 ← the target node
MOV GNVAL(R0),R2 ;R2 ← old value
MOV CHG.VNEW(RF),GNVAL(R0) ;stow the new value
MOV GNCHGS(R0),R3 ;R3 ← list of changers
BEQ CH.XXX ;if any
EVSIG GNEVT ;Leave the overall graph node critical region.
CH.1: JSR PC,CHNGER ;Call the next change routine
MOV NXTCHG(R3),R3 ;R3 ← next changer
BNE CH.1
EVWAIT GNEVT ;Enter critical section again.
CH.XXX: MOV CHG.ND(RF),R0 ;R0 ← the target node
CLR INVMRK(R0) ;Revalidate it
INC VALIDF(R0) ;Increment its validity count
EVSIG GNEVT ;Ok for others to enter critical code now.
MOV (SP)+,R3 ;Restore R3
MOV (SP)+,R2 ;Restore R2
RTS RF ;Return
CHNGER:
COMMENT ⊗ Calls the change routine indicated. This is done by
instantiating a new interpreter to do the work. It should terminate
the normal way, with a TERMINATE command. R2 points to the old
value, and CHG.VNEW(RF) points to the "new value". R3 points to the
changer cell. These values are put into the new ISB. GNODE
exclusion should be released before the call to CHNGER. Recall that
a changer cell looks like this:
XX NXTCHG ;next changer cell in chain
XX CHGISB ;unformed and void
XX CHGISB ;Points to interpreter status block to resolve addressing
XX CHGIPC ;the interpeter PC where the calculation starts
⊗
MOV R2,-(SP) ;Save R2
MOV R3,-(SP) ;Save R3
MOV R4,-(SP) ;Save R4
;make a new interpreter to do the work
MOV CHGISB(R3),R4 ;R4 ← ISB we have to emulate
MOV CHGIPC(R3),R0 ;R0 ← IPC of new ISB
EVMAK ;Stack a new event for communication with subsidiary
MOV (SP),R1 ;R1 ← copy of that event
JSR PC,SPAWN ;R0 ← Process decriptor
MOV PDBR4(R0),R4;R4 ← ISB of new interpreter
MOV R2,OLDV(R4) ;Stow the "old value" pointer in environment.
MOV CHG.VNEW(RF),NEWV(R4) ;Stow the "new value" pointer.
FORK R0,#INTERP,#1 ;Cause the new process to be started at high prio.
;clean up after the interpreter
EVWAIT ;Wait for the completion event (still on stack)
MOV (SP)+,R4 ;Restore R4
MOV (SP)+,R3 ;Restore R3
MOV (SP)+,R2 ;Restore R2
RTS PC ;Done
; ADDCHG
ROUTINE ADDCHG,<ACH.ND,ACH.CHG>
COMMENT ⊗ ACH.ND is the target graph node, and ACH.CHG is a changer
cell all prepared except for the link to the other changers. It
is necessary to perform this linking and to establish the dependencies
expressed by the needed list. ⊗
MOV R2,-(SP) ;Save R2
MOV ACH.ND(RF),R2 ;R2 ← LOC[target node]
MOV ACH.CHG(RF),R1 ;R1 ← LOC[changer cell]
EVWAIT GNEVT ;Enter critical region for graph nodes
MOV GNCHGS(R2),NXTCHG(R1) ;Link new changer into list
MOV R1,GNCHGS(R2) ;
EVSIG GNEVT ;Leave critical region
MOV (SP)+,R2 ;Restore R2
RTS R5 ;Done
; GETVAL, GETVR0
COMMENT ⊗ Called by the outside world. Returns LOC[value(GTV.ND)] in
R0 and the VALIDF in R1, after having scrounged around to get a valid
value, if necessary and possible. ⊗
ROUTINE GETVAL,<GTV.ND>
MOV GTV.ND(RF),R0
JSR PC,GETVR0
RTS RF
GETVR0: TST INVMRK(R0) ;Is the current value good?
BEQ GETV.R ;Yes
EVWAIT GNEVT ;No. Enter critical region.
MOV R0,-(SP) ;Stack R0
MOV RF,-(SP)
MOV R0,-(SP) ;EVALNODE(GTV.ND,TIME←TIME+1)
JSR PC,NXTTIM
MOV R0,-(SP)
MOV #MARK2,-(SP)
MOV SP,RF
JSR PC,EVALND
EVSIG GNEVT ;Leave critical region
MOV (SP)+,R0 ;R0 ← target node, now validated
GETV.R: MOV VALIDF(R0),R1 ;Get the validity count
MOV GNVAL(R0),R0 ;R0 ← value cell
RTS PC ;Done
; EVALND, EVLCLC
COMMENT ⊗ EVALND is a recursive procedure, which is given EVL.ND, the
target node to evaluate, and EVL.T, the "time" of evaluation. If
necessary, it calls itself at the same "time" to track down a chain
of related nodes. GNEVT exclusion should be on at the time that
this routine is first called, and will remain on after the return. ⊗
ROUTINE EVALND,<EVL.ND,EVL.T>
MOV EVL.ND(RF),R0 ;R0 ← target graph node
MOV INVMRK(R0),R1 ;Is the node already valid?
BEQ EV.RTS ;Yes
CMP R1,EVL.T(RF) ;No. Have we already looked at it this "time"?
BEQ EV.RTS ;Yes
MOV R2,-(SP) ;No. Save R2
MOV R3,-(SP) ;Save R3
MOV GNCLCS(R0),R2 ;R2 ← first calculator cell
BEQ EV.XXX ;if any
EV.CLP: MOV NEEDED(R2),R3 ;R3 ← needed list for this calculator
BEQ EV.NOK ;if any
EV.NLP: CALL EVALND,<DATUM(R3),EVL.T(RF)> ;Evaluate this need.
MOV DATUM(R3),R0 ;R0 ← graph node of the need
TST INVMRK(R0) ;Is it now valid?
BNE EV.NXC ;No. Will try the next calculator
MOV LINKF(R3),R3 ;Yes. R3 ← next needed cell
BNE EV.NLP ;If any.
EV.NOK: CALL EVLCLC,<R2> ;All needs are met. R0 ← Evaluate the calculator.
MOV EVL.ND(RF),R1 ;R1 ← target node
MOV R0,GNVAL(R1) ;Stow away its new value.
CLR INVMRK(R1) ;Mark it as valid.
INC VALIDF(R1) ;Increment its validity number
BR EV.XXX ;Ready to leave
EV.NXC: MOV NXTCLC(R2),R2 ;R2 ← next calculator cell
BNE EV.CLP ;if any
EV.XXX: MOV (SP)+,R3 ;Restore R3
MOV (SP)+,R2 ;Restore R2
EV.RTS: RTS RF ;Done
COMMENT ⊗ Each calculator has a field, CLCISB, which points to the
interpreter status block of its definition. This contains enough
information to resolve any variable references in the calculator.
EVLCLC calls the interpreter in a special way (through a CALL INTERP)
having first set up a pseudo-ISB in R4. When the interpreter
returns, the desired value should be on its R3 stack. Each time
EVLCLC is called, it constructs a new pseudo-ISB, uses it once, and
then releases it. This is somewhat wasteful, and could be cleaned
up. EVLCLC returns the value it found in R0. ⊗
ROUTINE EVLCLC,<EVC.CLC>
MOV R2,-(SP) ;Save R2
MOV R3,-(SP) ;Save R3
MOV R4,-(SP) ;Save R4
MOV #ISBS,R0 ;Get a pseudo-ISB
JSR PC,GTFREE ;R0 ← LOC[new ISB]
MOV R0,R4 ;R4 ← LOC[new ISB]
MOV EVC.CLC(RF),R2 ;
MOV CLCISB(R2),R1 ;R1 ← LOC[old ISB]
MOV ENV(R1),ENV(R4) ;Copy environments
MOV LEV(R1),LEV(R4) ;Copy levels
MOV CLCIPC(R2),IPC(R4) ;Initialize the IPC
MOV #INSTSZ,R0 ;
JSR PC,GTFREE ;R0 ← LOC[new interpreter stack]
MOV R0,-(SP) ;Save the stack location
ADD #INSTSZ,R0 ;
MOV R0,R3 ;R3 ← LOC[verge of new interpreter stack]
CALL INTERP ;Enter the interpreter, R0 ← LOC[new value cell]
MOV R0,R2 ;R2 ← LOC[new value cell]
MOV R4,R0 ;Release the ISB
JSR PC,RLFREE ;
MOV (SP)+,R0 ;Release the interpreter stack
JSR PC,RLFREE ;
MOV R2,R0 ;Result in R0.
MOV (SP)+,R4 ;Restore R4
MOV (SP)+,R3 ;Restore R3
MOV (SP)+,R2 ;Restore R2
RTS RF
; ADDCLC
ROUTINE ADDCLC,<ADD.ND,ADD.CLC>
COMMENT ⊗ ADD.ND is the target graph node, and ADD.CLC is a calculator
cell all prepared except for the link to the other calculators. It
is necessary to perform this linking and to establish the dependencies
expressed by the needed list. ⊗
MOV R2,-(SP) ;Save R2
MOV R3,-(SP) ;Save R3
MOV R4,-(SP) ;Save R4
MOV ADD.ND(RF),R3 ;R3 ← LOC[target node]
MOV ADD.CLC(RF),R1 ;R1 ← LOC[calculator cell]
EVWAIT GNEVT ;Enter critical region
MOV GNCLCS(R3),NXTCLC(R1) ;Link new calculator into list
MOV R1,GNCLCS(R3) ;
MOV NEEDED(R1),R2 ;R2 ← List of needed cells
BEQ ACLC.X ;If any
ACLC.1: JSR PC,GET2WD ;R0 ← new two-word cell for dependent list
MOV R3,DATUM(R0) ;Target node is now dependent of needed node
MOV DATUM(R2),R1 ;R1 ← next needed node
MOV GNDEPS(R1),LINKF(R0) ;Link new dependent into list
MOV R0,GNDEPS(R1) ;
MOV LINKF(R2),R2 ;R2 ← next needed cell
BNE ACLC.1 ;if any
ACLC.X: EVSIG GNEVT ;Leave critical region
MOV (SP)+,R4 ;Restore R4
MOV (SP)+,R3 ;Restore R3
MOV (SP)+,R2 ;Restore R2
RTS R5 ;Done
; MAKEGN, DELGN, DELGN1
MAKEGN:
COMMENT ⊗ Creates a graph node, with no frills (no calculators,
updaters, changers, loksh, boydem, tsibele, ...) except that if R0 is
non-zero, that is assumed to be the value cell pointer. The node
will be marked as invalid unless there was some value given. The
space is taken from large block storage. The new graph node is
returned in R0. ⊗
MOV R0,-(SP) ;Save R0
MOV #GNDSIZ,R0 ;
JSR PC,GTFREE ;R0 ← LOC[new graph node]
CLR INVMRK(R0) ;Validate the node
MOV (SP)+,GNVAL(R0) ;Stuff away the value cell pointer.
BNE MKGN2 ;Was there one?
MOV #-1,INVMRK(R0) ;No. Invalidate this node.
MKGN2: CLR GNDEPS(R0) ;Zero other fields
CLR GNCLCS(R0) ;
CLR GNCHGS(R0) ;
CLR NXTGN(R0) ;
CLR PRVGN(R0) ;
CLR VALIDF(R0) ;
EVWAIT GNEVT ;Critical section here
MOV GNODES,R1 ;Link up to other nodes in the world.
BEQ MKGN1 ;If any
MOV R1,NXTGN(R0);
MOV R0,PRVGN(R1);
MKGN1: MOV R0,GNODES ;
EVSIG GNEVT ;End of critical section
RTS PC ;
DELGN:
COMMENT ⊗ R0 is the location of the graph node. Destruction is a
very tricky business. All dependents must first be validated, then
each must be informed that its calculator has gone away. This means
that the pointer to the victim must not be removed from the
environment until this whole operation is finished. The value cell
must be reclaimed. This is taken care of by relying on the garbage
colector. Thus graph nodes may share value cells. Then the node
itself can be unlinked from the chain and returned to free storage. ⊗
MOV R2,-(SP) ;Save R2
MOV R3,-(SP) ;Save R3
MOV R0,R2 ;R2 ← Graph node to delete
EVWAIT GNEVT ;The whole procedure is critical.
;Handle the dependents
MOV GNDEPS(R2),R3 ;R3 ← List of dependents
BEQ DEL3 ;if any
JSR PC,NXTTIME ;R0 ← next "time"
MOV R0,-(SP) ;Save "time"
DEL2: MOV (SP),R0 ;Validate all dependents with this "time".
CALL EVALND,<DATUM(R3),R0>
CALL DELGN1,<DATUM(R3),R2> ;Remove calculator from dependent
MOV LINKF(R3),R3 ;R3 ← next dependent
BNE DEL2 ;if any
TST (SP)+ ;clean off stack
MOV GNDEPS(R2),R0 ;R0 ← list of dependents
JSR PC,RLLST1 ;Release the storage in the dependents list.
;Reclaim the calculator cells
DEL3: MOV GNCLCS(R2),R3 ;R3 ← First calculator cell
DEL4:: MOV R3,R0 ;R0 ← current calculator cell
BEQ DEL5 ;If any
MOV NXTCLC(R3),R3 ;R3 ← next calculator cell
JSR PC,RLFREE ;Release current one
BR DEL4 ;Do the others
;Reclaim the changer cells
DEL5: MOV GNCHGS(R2),R3 ;R3 ← First changer cell
DEL6:: MOV R3,R0 ;R0 ← current changer cell
BEQ DEL7 ;If any
MOV NXTCLC(R3),R3 ;R3 ← next changer cell
JSR PC,RLFREE ;Release current one
BR DEL6 ;Do the others
;Unlink this graph node
DEL7: MOV NXTGN(R2),R1 ;R1 ← forward link
MOV PRVGN(R2),R0 ;R0 ← backward link
MOV R0,PRVGN(R1) ;
MOV R1,NXTGN(R0) ;
EVSIG GNEVT ;Leave critical region.
MOV R2,R0 ;R0 ← target graph node hulk
JSR PC,RLFREE ;Release it.
MOV (SP)+,R3 ;Restore R3
MOV (SP)+,R2 ;Restore R2
RTS PC ;Done
ROUTINE DELGN1,<DLG.GN,DLG.GONE>;
COMMENT ⊗ The graph node pointed to by DLG.GN must have all
calculators removed that depend on DLG.GONE, another graph node.
Effort has already been made to validate DLG.GN, so no need to try it
again. Just find all such calculators, and get rid of them. It is
assumed that GNODE exclusion has been entered before this procedure
is called; it is still on when this procedure exits. ⊗
MOV R2,-(SP) ;Save R2
MOV R3,-(SP) ;Save R3
MOV DLG.GN(RF),R3 ;R3 ← victim node
MOV GNCLCS(R3),R2 ;R2 ← list of calculators
BEQ DELC1 ;If any
ADD #GNCLCS,R3 ;R3 ← LOC[list of calculators]
DELC6: MOV NEEDED(R2),R0 ;R0 ← First needed cell
BEQ DELC2 ;If any
DELC5: CMP DATUM(R0),DLG.GONE(RF) ;Is the departed mentioned in this will?
BNE DELC3 ;No
MOV R2,R0 ;R0 ← calculator cell to release
MOV NXTCLC(R2),R2 ;R2 ← next on list of calculators
MOV R2,(R3) ;And patch up the previous link
JSR PC,RLFREE ;Release the calculator cell
TST R2 ;Any more calculators?
BNE DELC6 ;Yes
BR DELC1 ;No
DELC3: MOV LINKF(R0),R0 ;R0 ← next needed cell
BNE DELC5 ;If any
DELC2: MOV R2,R3 ;
ADD #LINKF,R3 ;R3 ← LOC[previous link field in calculator list]
MOV NXTCLC(R3),R2 ;R2 ← next calculator
BNE DELC6 ;If any
DELC1: MOV (SP)+,R3 ;Restore R3
MOV (SP)+,R2 ;Restore R2
RTS RF ;Done
; RLLST1, RLLST2
RLLST1:
COMMENT ⊗ Free the storage used by the one-way list pointed to by R0.
Do not do anything to what is pointed to by DATUM(R0). Just CDR down
LINKF. ⊗
MOV R2,-(SP) ;Save R2
MOV R0,R2 ;R2 ← List header
BEQ RLLS11 ;If any
RLLS12:
MOV LINKF(R2),R2;R2 ← next in list
.IFNZ SMALLB
MOV #W2SPC,R1 ;
JSR PC,FRESBK ;Release
.IFF
JSR PC,RLFREE ;Release
.ENDC
MOV R2,R0 ;R0 ← next in list
BNE RLLS12 ;If any
RLLS11: MOV (SP)+,R2 ;Restore R2
RTS PC ;Done
RLLST2:
COMMENT ⊗ Free the storage used by the two-way list pointed to by R0.
Do not do anything to what is pointed to by DATUM(R0). Just CDR down
LINKF. ⊗
MOV R2,-(SP) ;Save R2
MOV R0,R2 ;R2 ← List header
BEQ RLLS21 ;If any
RLLS22:
MOV LINKF(R2),R2;R2 ← next in list
.IFNZ SMALLB
MOV #W3SPC,R1 ;
JSR PC,FRESBK ;Release
.IFF
JSR PC,RLFREE ;Release
.ENDC
MOV R2,R0 ;R0 ← next in list
BNE RLLS22 ;If any
RLLS21: MOV (SP)+,R2 ;Restore R2
RTS PC ;Done
; GET2WD, GET3WD
COMMENT ⊗ Get a cell for pointers, either of the two-word or
three-word variety. Note that JMP is used instead of JSR PC to
return to caller. ⊗
GET2WD: ;Get a 2-word cell
.IFNZ SMALLB
MOV #W2SPC,R0 ;
JMP GETSBK ;Allocate from small blocks
.IFF
MOV #2,R0 ;Number of words needed
JMP GTFREE ;R0 ← LOC[new block]
.ENDC
GET3WD: ;Get a 3-word cell
.IFNZ SMALLB
MOV #W3SPC,R0 ;
JMP GETSBK ;Allocate from small blocks
.IFF
MOV #3,R0 ;Number of words needed
JMP GTFREE ;R0 ← LOC[new block]
.ENDC
; W2SPC, W3SPC, MP2WD, MP3WD
; Two and Three word cell space definitions and marking methods
W2SPC: DEFSPC W2ID,MP2WD,2,20,0,20,25
W3SPC: DEFSPC W3ID,MP3WD,3,20,0,20,25
; At the moment, these are uncollectable spaces;
MP2WD: TSTB TAG(R0)
BNE MPRTS ;ALREADY DID THIS ONE
JSR PC,@2(RF) ;
MOV R2,-(SP) ;
MOV R0,R2 ;SAVE RESULT OF ROUT
MPDLF: MOV DATUM(R2),R0 ;DO DATUM
JSR PC,MARKR0 ;
MOV R0,DATUM(R2) ;
MOV LINKF(R2),R0 ;DO LINKF
JSR PC,MARKR0 ;A LONG LIST WILL PDLOV (ALAS)
MOV R0,LINKF(R2) ; BUT WE DONT HAVE ANY LONG LISTS (I HOPE)
MOV R2,R0 ;RETURN VALUE
MOV (SP)+,R2 ;
MPRTS: RTS PC
MP3WD: TSTB TAG(R0) ;DID WE DO THIS
BNE MPRTS ;YES
JSR PC,@2(RF) ;
MOV R2,-(SP)
MOV R0,R2
MOV LINKB(R2),R0 ;DO LINKB
JSR PC,MARKR0
MOV R0,LINKB(R2) ;
BR MPDLF ;GO DO DATUM & LINKF
; Known Bugs
COMMENT ⊗ It is possible that while a graph node is changed, a
changer is invoked. During its execution, some other process
modifies the change list for that node. When the changer is done, it
may get lost in the changer cell list. Graph node exclusion must be
turned off during execution of a changer, so that it can change other
cells. Special changer exclusion causes deadlock in the case that
one changer triggers another.
Certain variables, like YELLOW and BLUE, are not being initialized
to anything.
⊗